﻿using GN.Pay.Client.Utils;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GN.Pay.Client.Response
{
    /// <summary>
    /// 支付响应
    /// </summary>
    [Serializable]
    public class PayJsonResponse : Dictionary<string, JToken>
    {
        /// <summary>
        /// 实例化 JsonDictionary 类新实例
        /// </summary>
        public PayJsonResponse()
            : base(StringComparer.InvariantCultureIgnoreCase)
        {
        }

        /// <summary>
        /// 检查必须参数
        /// </summary>
        /// <param name="name">名称</param>
        /// <returns></returns>
        public JToken CheckMustResponseParam(string name)
        {
            JToken value;
            if (!TryGetValue(name, out value) || value == null)
            {
                throw new PayException(Consts.SERVER_RESPONSE_LACK_PARAM,
                    "服务器响应错误，缺少 " + name + "参数");
            }
            return value;
        }

        /// <summary>
        /// 获取结果，不存在则返回 null
        /// </summary>
        /// <returns></returns>
        public JToken Result()
        {
            return Get("result");
        }

        /// <summary>
        /// 转为结果
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <returns></returns>
        public T ToResult<T>()
        {
            var result = Result();
            if (result == null)
            {
                return default(T);
            }
            return result.ToObject<T>();
        }

        /// <summary>
        /// 转为结果
        /// </summary>
        /// <param name="objectType">对象类型</param>
        /// <returns></returns>
        public object ToResult(Type objectType)
        {
            var result = Result();
            if (result == null)
            {
                return null;
            }
            return result.ToObject(objectType);
        }

        /// <summary>
        /// 获取结果，不存在则返回 null
        /// </summary>
        /// <param name="name">名称</param>
        /// <returns></returns>
        public JToken Get(string name)
        {
            JToken result = null;
            TryGetValue(name, out result);
            return result;
        }

        /// <summary>
        /// 必须布尔参数
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public bool MustBooleanResponseParam(string name)
        {
            var value = CheckMustResponseParam(name);
            try
            {
                return (bool)value;
            }
            catch (Exception)
            {
                throw new PayException(Consts.SERVER_RESPONSE_PARAM_FROMAT_ERROR,
                  "参数 " + name + " 格式错误");
            }
        }

        /// <summary>
        /// 字符参数
        /// </summary>
        /// <param name="jToken"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private string StringResponseParam(JToken jToken, string name)
        {
            try
            {
                return (string)jToken;
            }
            catch (Exception)
            {
                throw new PayException(Consts.SERVER_RESPONSE_PARAM_FROMAT_ERROR,
                  "参数 " + name + " 格式错误");
            }
        }

        /// <summary>
        /// 必须字符参数
        /// </summary>
        /// <param name="name">名称</param>
        /// <returns></returns>
        public string MustStringResponseParam(string name)
        {
            var value = CheckMustResponseParam(name);
            return StringResponseParam(value, name);
        }

        /// <summary>
        /// int32参数
        /// </summary>
        /// <param name="jToken"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private int Int32ResponseParam(JToken jToken, string name)
        {
            try
            {
                return (int)jToken;
            }
            catch (Exception)
            {
                throw new PayException(Consts.SERVER_RESPONSE_PARAM_FROMAT_ERROR,
                  "参数 " + name + " 格式错误");
            }
        }

        /// <summary>
        /// 必须int32参数
        /// </summary>
        /// <param name="name">名称</param>
        /// <returns></returns>
        public int MustInt32ResponseParam(string name)
        {
            var value = CheckMustResponseParam(name);
            return Int32ResponseParam(value, name);
        }

        /// <summary>
        /// 必须int64参数
        /// </summary>
        /// <param name="name">名称</param>
        /// <returns></returns>
        public long MustInt64ResponseParam(string name)
        {
            var value = CheckMustResponseParam(name);
            try
            {
                return (long)value;
            }
            catch (Exception)
            {
                throw new PayException(Consts.SERVER_RESPONSE_PARAM_FROMAT_ERROR,
                  "参数 " + name + " 格式错误");
            }
        }

        /// <summary>
        /// 获取是否成功
        /// </summary>
        /// <returns></returns>
        public bool IsSuccess()
        {
            return MustBooleanResponseParam("success");
        }

        /// <summary>
        /// 错误检查
        /// </summary>
        public void ErrorCheck()
        {
            if (!IsSuccess())
            {
                JToken error = Get("error");
                if (error == null)
                {
                    throw new PayException(0, "请求结果发生未知错误");
                }
                JObject job = error as JObject;
                if (job == null)
                {
                    throw new PayException(Consts.SERVER_RESPONSE_PARAM_FROMAT_ERROR,
                  "参数 error 格式错误");
                }
                var jCode = job.GetValue("code");
                int code = 0;
                if (jCode != null)
                {
                    code = Int32ResponseParam(jCode, "error.code");
                }
                var jMessage = job.GetValue("message");
                string message = "未知错误";
                if (jMessage != null)
                {
                    message = StringResponseParam(jMessage, "error.message");
                }
                throw new PayException(code, message);
            }
        }

        private readonly string signIndexStr = "\"sign\":\"{0}\"";

        /// <summary>
        /// 获取签名内容
        /// </summary>
        /// <param name="resultString">结果</param>
        /// <param name="sign">签名</param>
        /// <returns></returns>
        private string GetSignContent(string resultString, string sign)
        {
            StringBuilder sb = new StringBuilder();
            string sginStr = string.Format(signIndexStr, sign);
            string str = sginStr + ",";
            int index = resultString.IndexOf(str);
            if (index < 0)
            {
                str = "," + sginStr;
                index = resultString.IndexOf("," + sginStr);
            }
            if (index >= 0)
            {
                if (index > 0)
                {
                    sb.Append(resultString.Substring(0, index));
                }
                int lastIndex = index + str.Length;
                if (resultString.Length > lastIndex)
                {
                    sb.Append(resultString.Substring(lastIndex));
                }
            }
            return sb.ToString();
        }

        /// <summary>
        /// 签名检查
        /// </summary>
        /// <param name="key"></param>
        /// <param name="resultString"></param>
        public void SginVerify(string key, string resultString)
        {
            MustStringResponseParam("appId");
            MustStringResponseParam("merchantId");
            MustStringResponseParam("randomStr");
            string sign = MustStringResponseParam("sign");
            long timeStamp = MustInt64ResponseParam("timeStamp");
            string signContent = GetSignContent(resultString, sign);
            bool success = MustBooleanResponseParam("success");
            if (Math.Abs(timeStamp - DateTimeUtils.CurrentUtcTimeStampMilliseconds) > Consts.SERVER_RESPONSE_TIMESTAMP_DIFF_MILLISECONDS)
            {
                throw new PayException(Consts.SERVER_RESPONSE_TIMESTAMP_DIFF_MILLISECONDS,
                    "服务器响应错误，TimeStamp 大于限定范围");
            }
            string content = signContent + "&key=" + key;
            if (!Md5Utils.SginVerify(content, sign, Encoding.UTF8))
            {
                throw new PayException(Consts.SERVER_RESPONSE_SING_ERROR,
                    "服务器响应签名不正确");
            }
        }
    }
}
